#ifndef _FRUSTUM_H_
#define _FRUSTUM_H_

#include "..\cuCommon\cuTypes.h"
#include "..\cuCommon\cuMath.h"

#include "C_Camera.h"
#include "Plane.h"

struct SGeometryFrustum
{
    float3 m_Vertex[5];
    float3 m_Color[5];
    int m_VtxIdx[6];
    int m_ClrIdx[6];
};

SDH_CALL
SGeometryFrustum * GeometryFrustum(SGeometryFrustum * pGeomFrustum, C_Camera & camera)
{
#ifdef _DEBUG
	if (pGeomFrustum == NULL) return NULL;
#endif
	float3 eye = camera.Eye();
	float3 dir = camera.Dir();
	float3 up = camera.Up();
	float zn = camera.zN();
	float zf = camera.zF();
	float asp = camera.Asp();
	float fov = camera.FoV();

  pGeomFrustum->m_Vertex[0] = eye;

  float3 fc = eye + dir * zf;

  float3 right = camera.Right();

  float Wf = zf * tan( to_radian( fov ) * 0.5f);
  float Hf = Wf / asp;

  pGeomFrustum->m_Vertex[1] = fc - right * Wf - up * Hf;
  pGeomFrustum->m_Vertex[2] = fc - right * Wf + up * Hf;
  pGeomFrustum->m_Vertex[3] = fc + right * Wf + up * Hf;
  pGeomFrustum->m_Vertex[4] = fc + right * Wf - up * Hf;

  pGeomFrustum->m_VtxIdx[0] = 0;
  pGeomFrustum->m_VtxIdx[1] = 1;
  pGeomFrustum->m_VtxIdx[2] = 2;
  pGeomFrustum->m_VtxIdx[3] = 3;
  pGeomFrustum->m_VtxIdx[4] = 4;
  pGeomFrustum->m_VtxIdx[5] = 1;

  set(pGeomFrustum->m_Color[0], 1.0f, 1.0f, 1.0f);
  set(pGeomFrustum->m_Color[1], 1.0f, 0.0f, 0.0f);
  set(pGeomFrustum->m_Color[2], 1.0f, 0.0f, 0.0f);
  set(pGeomFrustum->m_Color[3], 1.0f, 0.0f, 0.0f);
  set(pGeomFrustum->m_Color[4], 1.0f, 0.0f, 0.0f);

  pGeomFrustum->m_ClrIdx[0] = 0;
  pGeomFrustum->m_ClrIdx[1] = 1;
  pGeomFrustum->m_ClrIdx[2] = 1;
  pGeomFrustum->m_ClrIdx[3] = 1;
  pGeomFrustum->m_ClrIdx[4] = 1;
  pGeomFrustum->m_ClrIdx[5] = 1;

  return pGeomFrustum;
}

struct SFrustum
{
    SPlane m_Plane[6];
};

SDH_CALL
SFrustum * Frustum(SFrustum * pFrustum, C_Camera & camera)
{
#ifdef _DEBUG
	if (pFrustum == NULL) return NULL;
#endif

  float3 planePoint [4];
  float3 planeNormal[4];

	float3 eye = camera.Eye();
	float3 dir = camera.Dir();
	float3 up  = camera.Up();
	float  zn  = camera.zN();
	float  zf  = camera.zF();
  float  asp = camera.Asp();
	float  fov = camera.FoV();

  float3 np = eye + dir * zn;  // near plane 
  float3 fp = eye + dir * zf;  // far plane 

  float3 right = camera.Right();

  float tangent = tan( to_radian( fov ) * 0.5f);
  float Wn = zn * tangent;  // width on near plane
  float Hn = Wn / asp;      // height on near plane
  float Wf = zf * tangent;  // width on far plane
  float Hf = Wf / asp;      // height on far plane

  planePoint[0] = fp - right * Wf;
  planePoint[1] = fp + right * Wf;
  planePoint[2] = fp + up * Hf;
  planePoint[3] = fp - up * Hf;

  planeNormal[0] = cross( up,    normalize(planePoint[0] - eye) );
  planeNormal[1] = cross( up,    normalize(planePoint[1] - eye) );
  planeNormal[2] = cross( right, normalize(planePoint[2] - eye) );
  planeNormal[3] = cross( right, normalize(planePoint[3] - eye) );

  Plane( &pFrustum->m_Plane[0],  dir, np ); // normal points inside the frustum volume
  
  Plane( &pFrustum->m_Plane[1],  planeNormal[0], planePoint[0] ); // normal points inside the frustum volume
  Plane( &pFrustum->m_Plane[2], -planeNormal[1], planePoint[1] ); // normal points inside the frustum volume
  Plane( &pFrustum->m_Plane[3],  planeNormal[2], planePoint[2] ); // normal points inside the frustum volume
  Plane( &pFrustum->m_Plane[4], -planeNormal[3], planePoint[3] ); // normal points inside the frustum volume

  Plane( &pFrustum->m_Plane[5], -dir, fp ); // normal points inside the frustum volume

  return pFrustum;
}

SDH_CALL
SFrustum * Frustum(SFrustum * pFrustum, const matrix4x4 & vp, bool bColumn = true)
{
#ifdef _DEBUG
	if (pFrustum == NULL) return NULL;
#endif
  if (bColumn)
  {
    float4 column [4] = 
    {
      { vp.row[0].x, vp.row[1].x, vp.row[2].x, vp.row[3].x },
      { vp.row[0].y, vp.row[1].y, vp.row[2].y, vp.row[3].y },
      { vp.row[0].z, vp.row[1].z, vp.row[2].z, vp.row[3].z },
      { vp.row[0].w, vp.row[1].w, vp.row[2].w, vp.row[3].w }
    };

    float4 pn =  column[2] + column[3];
    Plane( &pFrustum->m_Plane[0], pn.x, pn.y, pn.z, pn.w );

    float4 pr = -column[0] + column[3];
    Plane( &pFrustum->m_Plane[1], pr.x, pr.y, pr.z, pr.w );

    float4 pl =  column[0] + column[3];
    Plane( &pFrustum->m_Plane[2], pl.x, pl.y, pl.z, pl.w );

    float4 pt = -column[1] + column[3];
    Plane( &pFrustum->m_Plane[3], pt.x, pt.y, pt.z, pt.w );

    float4 pb =  column[1] + column[3];
    Plane( &pFrustum->m_Plane[4], pb.x, pb.y, pb.z, pb.w );

    float4 pf = -column[2] + column[3];
    Plane( &pFrustum->m_Plane[5], pf.x, pf.y, pf.z, pf.w );
  }
  else
  {
    float4 pn =  vp.row[2] + vp.row[3];
    Plane( &pFrustum->m_Plane[0], pn.x, pn.y, pn.z, pn.w );

    float4 pr = -vp.row[0] + vp.row[3];
    Plane( &pFrustum->m_Plane[1], pr.x, pr.y, pr.z, pr.w );

    float4 pl =  vp.row[0] + vp.row[3];
    Plane( &pFrustum->m_Plane[2], pl.x, pl.y, pl.z, pl.w );

    float4 pt = -vp.row[1] + vp.row[3];
    Plane( &pFrustum->m_Plane[3], pt.x, pt.y, pt.z, pt.w );

    float4 pb =  vp.row[1] + vp.row[3];
    Plane( &pFrustum->m_Plane[4], pb.x, pb.y, pb.z, pb.w );

    float4 pf = -vp.row[2] + vp.row[3];
    Plane( &pFrustum->m_Plane[5], pf.x, pf.y, pf.z, pf.w );
  }
  return pFrustum;
}

SDH_CALL
int Check(const SFrustum & frustum, const float3 & point)
{
  int check = 1;

  check = ( HemiSpace(frustum.m_Plane[0], point) > -1 ) && 
          ( HemiSpace(frustum.m_Plane[1], point) > -1 ) && 
          ( HemiSpace(frustum.m_Plane[2], point) > -1 ) && 
          ( HemiSpace(frustum.m_Plane[3], point) > -1 ) && 
          ( HemiSpace(frustum.m_Plane[4], point) > -1 ) && 
          ( HemiSpace(frustum.m_Plane[5], point) > -1 );

  return check;
}

#endif